FIND_PATH(libnl-tiny_include_dir netlink-generic.h PATH_SUFFIXES libnl-tiny)
INCLUDE_DIRECTORIES(${ubox_include_dir} ${libnl-tiny_include_dir})
+FIND_LIBRARY(json NAMES json-c)
FIND_LIBRARY(libnl NAMES nl-tiny)
add_definitions(-D_GNU_SOURCE -Os -Wall -Werror --std=gnu99)
endif(${DHCPV4_SUPPORT})
add_executable(odhcpd src/odhcpd.c src/config.c src/router.c src/dhcpv6.c src/ndp.c src/dhcpv6-ia.c src/dhcpv6-pxe.c src/netlink.c ${EXT_SRC})
-target_link_libraries(odhcpd resolv ubox uci ${libnl} ${EXT_LINK})
+target_link_libraries(odhcpd resolv ubox uci ${json} ${libnl} ${EXT_LINK})
# Installation
install(TARGETS odhcpd DESTINATION sbin/)
leasetrigger string Lease trigger script
hostsfile string DHCP/v6 hostfile
loglevel integer 6 Syslog level priority (0-7)
+piofolder string Folder to store IPv6 prefix information (to
+ detect stale prefixes, see RFC9096, §3.5)
Sections of type dhcp (configure DHCP / DHCPv6 / RA / NDP service)
+#include <errno.h>
#include <fcntl.h>
#include <resolv.h>
#include <signal.h>
#include <uci.h>
#include <uci_blob.h>
+#include <json-c/json.h>
#include <libubox/utils.h>
#include <libubox/avl.h>
#include <libubox/avl-cmp.h>
struct vlist_tree leases = VLIST_TREE_INIT(leases, lease_cmp, lease_update, true, false);
AVL_TREE(interfaces, avl_strcmp, false, NULL);
-struct config config = {.legacy = false, .main_dhcpv4 = false,
- .dhcp_cb = NULL, .dhcp_statefile = NULL, .dhcp_hostsfile = NULL,
- .log_level = LOG_WARNING};
+struct config config = {
+ .legacy = false,
+ .main_dhcpv4 = false,
+ .dhcp_cb = NULL,
+ .dhcp_statefile = NULL,
+ .dhcp_hostsfile = NULL,
+ .ra_piofolder = NULL,
+ .ra_piofolder_fd = -1,
+ .log_level = LOG_WARNING,
+};
#define START_DEFAULT 100
#define LIMIT_DEFAULT 150
ODHCPD_ATTR_LEASETRIGGER,
ODHCPD_ATTR_LOGLEVEL,
ODHCPD_ATTR_HOSTSFILE,
+ ODHCPD_ATTR_PIOFOLDER,
ODHCPD_ATTR_MAX
};
[ODHCPD_ATTR_LEASETRIGGER] = { .name = "leasetrigger", .type = BLOBMSG_TYPE_STRING },
[ODHCPD_ATTR_LOGLEVEL] = { .name = "loglevel", .type = BLOBMSG_TYPE_INT32 },
[ODHCPD_ATTR_HOSTSFILE] = { .name = "hostsfile", .type = BLOBMSG_TYPE_STRING },
+ [ODHCPD_ATTR_PIOFOLDER] = { .name = "piofolder", .type = BLOBMSG_TYPE_STRING },
};
const struct uci_blob_param_list odhcpd_attr_list = {
iface->ra_mininterval = iface->ra_maxinterval/3;
iface->ra_lifetime = -1;
iface->ra_dns = true;
+ iface->pio_update = false;
}
static void clean_interface(struct interface *iface)
clean_interface(iface);
free(iface->addr4);
free(iface->addr6);
- free(iface->invalid_addr6);
+ free(iface->pios);
free(iface->ifname);
free(iface);
}
config.dhcp_hostsfile = strdup(blobmsg_get_string(c));
}
+ if ((c = tb[ODHCPD_ATTR_PIOFOLDER])) {
+ free(config.ra_piofolder);
+ config.ra_piofolder = strdup(blobmsg_get_string(c));
+ }
+
if ((c = tb[ODHCPD_ATTR_LEASETRIGGER])) {
free(config.dhcp_cb);
config.dhcp_cb = strdup(blobmsg_get_string(c));
}
}
+ config_load_ra_pio(iface);
+
return 0;
err:
return ipv6_pxe_entry_new(arch, url) ? -1 : 0;
}
+#define JSON_LENGTH "length"
+#define JSON_PREFIX "prefix"
+#define JSON_SLAAC "slaac"
+#define JSON_TIME "time"
+
+static inline time_t config_time_from_json(time_t json_time)
+{
+ time_t ref, now;
+
+ ref = time(NULL);
+ now = odhcpd_time();
+
+ if (now > json_time || ref > json_time)
+ return 0;
+
+ return json_time + (now - ref);
+}
+
+static inline time_t config_time_to_json(time_t config_time)
+{
+ time_t ref, now;
+
+ ref = time(NULL);
+ now = odhcpd_time();
+
+ return config_time + (ref - now);
+}
+
+static inline bool config_ra_pio_enabled(struct interface *iface)
+{
+ return config.ra_piofolder_fd >= 0 && iface->ra == MODE_SERVER && !iface->master;
+}
+
+static bool config_ra_pio_time(json_object *slaac_json, time_t *slaac_time)
+{
+ time_t pio_json_time, pio_time;
+ json_object *time_json;
+
+ time_json = json_object_object_get(slaac_json, JSON_TIME);
+ if (!time_json)
+ return true;
+
+ pio_json_time = (time_t) json_object_get_int64(time_json);
+ if (!pio_json_time)
+ return true;
+
+ pio_time = config_time_from_json(pio_json_time);
+ if (!pio_time)
+ return false;
+
+ *slaac_time = pio_time;
+
+ return true;
+}
+
+static json_object *config_load_ra_pio_json(struct interface *iface)
+{
+ json_object *json;
+ int fd;
+
+ fd = openat(config.ra_piofolder_fd, iface->ifname, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return NULL;
+
+ json = json_object_from_fd(fd);
+
+ close(fd);
+
+ if (!json)
+ syslog(LOG_ERR,
+ "rfc9096: %s: json read error %s",
+ iface->ifname,
+ json_util_get_last_err());
+
+ return json;
+}
+
+void config_load_ra_pio(struct interface *iface)
+{
+ json_object *json, *slaac_json;
+ size_t pio_cnt;
+ time_t now;
+
+ if (!config_ra_pio_enabled(iface))
+ return;
+
+ json = config_load_ra_pio_json(iface);
+ if (!json)
+ return;
+
+ slaac_json = json_object_object_get(json, JSON_SLAAC);
+ if (!slaac_json) {
+ json_object_put(json);
+ return;
+ }
+
+ now = odhcpd_time();
+
+ pio_cnt = json_object_array_length(slaac_json);
+ iface->pios = malloc(sizeof(struct ra_pio) * pio_cnt);
+ if (!iface->pios) {
+ json_object_put(json);
+ return;
+ }
+
+ iface->pio_cnt = 0;
+ for (size_t i = 0; i < pio_cnt; i++) {
+ json_object *cur_pio_json, *length_json, *prefix_json;
+ const char *pio_str;
+ time_t pio_lt = 0;
+ struct ra_pio *pio;
+ uint8_t pio_len;
+
+ cur_pio_json = json_object_array_get_idx(slaac_json, i);
+ if (!cur_pio_json)
+ continue;
+
+ if (!config_ra_pio_time(cur_pio_json, &pio_lt))
+ continue;
+
+ length_json = json_object_object_get(cur_pio_json, JSON_LENGTH);
+ if (!length_json)
+ continue;
+
+ prefix_json = json_object_object_get(cur_pio_json, JSON_PREFIX);
+ if (!prefix_json)
+ continue;
+
+ pio_len = (uint8_t) json_object_get_uint64(length_json);
+ pio_str = json_object_get_string(prefix_json);
+ pio = &iface->pios[iface->pio_cnt];
+
+ inet_pton(AF_INET6, pio_str, &pio->prefix);
+ pio->length = pio_len;
+ pio->lifetime = pio_lt;
+ syslog(LOG_INFO,
+ "rfc9096: %s: load %s/%u (%u)",
+ iface->ifname,
+ pio_str,
+ pio_len,
+ ra_pio_lifetime(pio, now));
+
+ iface->pio_cnt++;
+ }
+
+ json_object_put(json);
+
+ if (!iface->pio_cnt) {
+ free(iface->pios);
+ iface->pios = NULL;
+ } else if (iface->pio_cnt != pio_cnt) {
+ iface->pios = realloc(iface->pios, sizeof(struct ra_pio) * iface->pio_cnt);
+ }
+}
+
+static void config_save_ra_pio_json(struct interface *iface, struct json_object *json)
+{
+ size_t tmp_piofile_strlen;
+ char *tmp_piofile;
+ int fd, ret;
+
+ tmp_piofile_strlen = strlen(iface->ifname) + 2;
+ tmp_piofile = alloca(tmp_piofile_strlen);
+ snprintf(tmp_piofile, tmp_piofile_strlen, ".%s", iface->ifname);
+
+ fd = openat(config.ra_piofolder_fd,
+ tmp_piofile,
+ O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC,
+ 0644);
+ if (fd < 0) {
+ syslog(LOG_ERR,
+ "rfc9096: %s: error %m creating temporary json file",
+ iface->ifname);
+ return;
+ }
+
+ ret = json_object_to_fd(fd, json, JSON_C_TO_STRING_PLAIN);
+ if (ret) {
+ syslog(LOG_ERR,
+ "rfc9096: %s: json write error %s",
+ iface->ifname,
+ json_util_get_last_err());
+ close(fd);
+ unlinkat(config.ra_piofolder_fd, tmp_piofile, 0);
+ return;
+ }
+
+ ret = fsync(fd);
+ if (ret) {
+ syslog(LOG_ERR,
+ "rfc9096: %s: error %m syncing %s",
+ iface->ifname,
+ tmp_piofile);
+ close(fd);
+ unlinkat(config.ra_piofolder_fd, tmp_piofile, 0);
+ return;
+ }
+
+ ret = close(fd);
+ if (ret) {
+ syslog(LOG_ERR,
+ "rfc9096: %s: error %m closing %s",
+ iface->ifname,
+ tmp_piofile);
+ unlinkat(config.ra_piofolder_fd, tmp_piofile, 0);
+ return;
+ }
+
+ ret = renameat(config.ra_piofolder_fd,
+ tmp_piofile,
+ config.ra_piofolder_fd,
+ iface->ifname);
+ if (ret) {
+ syslog(LOG_ERR,
+ "rfc9096: %s: error %m renaming piofile: %s -> %s",
+ iface->ifname,
+ tmp_piofile,
+ iface->ifname);
+ close(fd);
+ unlinkat(config.ra_piofolder_fd, tmp_piofile, 0);
+ return;
+ }
+
+ iface->pio_update = false;
+ syslog(LOG_WARNING,
+ "rfc9096: %s: piofile updated",
+ iface->ifname);
+}
+
+void config_save_ra_pio(struct interface *iface)
+{
+ struct json_object *json, *slaac_json;
+ char ipv6_str[INET6_ADDRSTRLEN];
+ time_t now;
+
+ if (!config_ra_pio_enabled(iface))
+ return;
+
+ if (!iface->pio_update)
+ return;
+
+ now = odhcpd_time();
+
+ json = json_object_new_object();
+ if (!json)
+ return;
+
+ slaac_json = json_object_new_array_ext(iface->pio_cnt);
+ if (!slaac_json) {
+ json_object_put(slaac_json);
+ return;
+ }
+
+ json_object_object_add(json, JSON_SLAAC, slaac_json);
+
+ for (size_t i = 0; i < iface->pio_cnt; i++) {
+ struct json_object *cur_pio_json, *len_json, *pfx_json;
+ const struct ra_pio *cur_pio = &iface->pios[i];
+
+ if (ra_pio_expired(cur_pio, now))
+ continue;
+
+ cur_pio_json = json_object_new_object();
+ if (!cur_pio_json)
+ continue;
+
+ inet_ntop(AF_INET6, &cur_pio->prefix, ipv6_str, sizeof(ipv6_str));
+
+ pfx_json = json_object_new_string(ipv6_str);
+ if (!pfx_json) {
+ json_object_put(cur_pio_json);
+ continue;
+ }
+
+ len_json = json_object_new_uint64(cur_pio->length);
+ if (!len_json) {
+ json_object_put(cur_pio_json);
+ json_object_put(pfx_json);
+ continue;
+ }
+
+ json_object_object_add(cur_pio_json, JSON_PREFIX, pfx_json);
+ json_object_object_add(cur_pio_json, JSON_LENGTH, len_json);
+
+ if (cur_pio->lifetime) {
+ struct json_object *time_json;
+ time_t pio_lt;
+
+ pio_lt = config_time_to_json(cur_pio->lifetime);
+
+ time_json = json_object_new_int64(pio_lt);
+ if (time_json)
+ json_object_object_add(cur_pio_json, JSON_TIME, time_json);
+ }
+
+ json_object_array_add(slaac_json, cur_pio_json);
+ }
+
+ config_save_ra_pio_json(iface, json);
+
+ json_object_put(json);
+}
+
void odhcpd_reload(void)
{
struct uci_context *uci = uci_alloc_context();
free(path);
}
+ if (config.ra_piofolder) {
+ char *path = strdupa(config.ra_piofolder);
+
+ mkdir_p(path, 0755);
+
+ close(config.ra_piofolder_fd);
+ config.ra_piofolder_fd = open(path, O_PATH | O_DIRECTORY | O_CLOEXEC);
+ if (config.ra_piofolder_fd < 0)
+ syslog(LOG_ERR, "Unable to open piofolder '%s': %m", path);
+ }
+
vlist_flush(&leases);
#ifdef WITH_UBUS
addr[i].valid_lt < iface->addr6[i].valid_lt || addr[i].preferred_lt < iface->addr6[i].preferred_lt)
change = true;
}
-
- if (change) {
- /*
- * Keep track of removed prefixes, so we could advertise them as invalid
- * for at least a couple of times.
- *
- * RFC7084 § 4.3 :
- * L-13: If the delegated prefix changes, i.e., the current prefix is
- * replaced with a new prefix without any overlapping time
- * period, then the IPv6 CE router MUST immediately advertise the
- * old prefix with a Preferred Lifetime of zero and a Valid
- * Lifetime of either a) zero or b) the lower of the current
- * Valid Lifetime and two hours (which must be decremented in
- * real time) in a Router Advertisement message as described in
- * Section 5.5.3, (e) of [RFC4862].
- */
-
- for (size_t i = 0; i < iface->addr6_len; ++i) {
- bool removed = true;
-
- if (iface->addr6[i].valid_lt <= (uint32_t)now)
- continue;
-
- for (ssize_t j = 0; removed && j < len; ++j) {
- size_t plen = min(addr[j].prefix, iface->addr6[i].prefix);
-
- if (odhcpd_bmemcmp(&addr[j].addr.in6, &iface->addr6[i].addr.in6, plen) == 0)
- removed = false;
- }
-
- for (size_t j = 0; removed && j < iface->invalid_addr6_len; ++j) {
- size_t plen = min(iface->invalid_addr6[j].prefix, iface->addr6[i].prefix);
-
- if (odhcpd_bmemcmp(&iface->invalid_addr6[j].addr.in6, &iface->addr6[i].addr.in6, plen) == 0)
- removed = false;
- }
-
- if (removed) {
- size_t pos = iface->invalid_addr6_len;
- struct odhcpd_ipaddr *new_invalid_addr6 = realloc(iface->invalid_addr6,
- sizeof(*iface->invalid_addr6) * (pos + 1));
-
- if (!new_invalid_addr6)
- break;
-
- iface->invalid_addr6 = new_invalid_addr6;
- iface->invalid_addr6_len++;
- memcpy(&iface->invalid_addr6[pos], &iface->addr6[i], sizeof(*iface->invalid_addr6));
- iface->invalid_addr6[pos].valid_lt = iface->invalid_addr6[pos].preferred_lt = (uint32_t)now;
-
- if (iface->invalid_addr6[pos].prefix < 64)
- iface->invalid_addr6[pos].prefix = 64;
- }
- }
- }
}
iface->addr6 = addr;
/* ipv6 only */
struct {
uint8_t dprefix;
- uint8_t invalid_advertisements;
bool tentative;
};
char *dhcp_cb;
char *dhcp_statefile;
char *dhcp_hostsfile;
+
+ char *ra_piofolder;
+ int ra_piofolder_fd;
+
int log_level;
};
};
+// RA PIO - RFC9096
+struct ra_pio {
+ struct in6_addr prefix;
+ uint8_t length;
+ time_t lifetime;
+};
+
+
struct interface {
struct avl_node avl;
// IPv6 runtime data
struct odhcpd_ipaddr *addr6;
size_t addr6_len;
- struct odhcpd_ipaddr *invalid_addr6;
- size_t invalid_addr6_len;
// RA runtime data
struct odhcpd_event router_event;
// DNR
struct dnr_options *dnr;
size_t dnr_cnt;
+
+ // RA PIO - RFC9096
+ struct ra_pio *pios;
+ size_t pio_cnt;
+ bool pio_update;
};
extern struct avl_tree interfaces;
return a;
}
+inline static bool ra_pio_expired(const struct ra_pio *pio, time_t now)
+{
+ return pio->lifetime && (now > pio->lifetime);
+}
+
+inline static uint32_t ra_pio_lifetime(const struct ra_pio *pio, time_t now)
+{
+ if (!pio->lifetime || now > pio->lifetime)
+ return 0;
+
+ return (uint32_t) (pio->lifetime - now);
+}
+
+inline static bool ra_pio_stale(const struct ra_pio *pio)
+{
+ return !!pio->lifetime;
+}
+
// Exported main functions
int odhcpd_register(struct odhcpd_event *event);
int odhcpd_deregister(struct odhcpd_event *event);
struct lease *config_find_lease_by_mac(const uint8_t *mac);
struct lease *config_find_lease_by_hostid(const uint64_t hostid);
struct lease *config_find_lease_by_ipaddr(const uint32_t ipaddr);
+void config_load_ra_pio(struct interface *iface);
+void config_save_ra_pio(struct interface *iface);
int set_lease_from_blobmsg(struct blob_attr *ba);
#ifdef WITH_UBUS
uint8_t body[];
};
+/* IPv6 RA PIOs */
+static struct ra_pio *router_find_ra_pio(struct interface *iface,
+ struct nd_opt_prefix_info *p)
+{
+ for (size_t i = 0; i < iface->pio_cnt; i++) {
+ struct ra_pio *cur_pio = &iface->pios[i];
+
+ if (p->nd_opt_pi_prefix_len == cur_pio->length &&
+ !odhcpd_bmemcmp(&p->nd_opt_pi_prefix, &cur_pio->prefix, cur_pio->length))
+ return cur_pio;
+ }
+
+ return NULL;
+}
+
+static void router_add_ra_pio(struct interface *iface,
+ struct nd_opt_prefix_info *p)
+{
+ char ipv6_str[INET6_ADDRSTRLEN];
+ struct ra_pio *new_pios, *pio;
+
+ pio = router_find_ra_pio(iface, p);
+ if (pio) {
+ if (pio->lifetime) {
+ pio->lifetime = 0;
+
+ iface->pio_update = true;
+ syslog(LOG_WARNING, "rfc9096: %s: renew %s/%u",
+ iface->ifname,
+ inet_ntop(AF_INET6, &pio->prefix, ipv6_str, sizeof(ipv6_str)),
+ pio->length);
+ }
+
+ return;
+ }
+
+ new_pios = realloc(iface->pios, sizeof(struct ra_pio) * (iface->pio_cnt + 1));
+ if (!new_pios)
+ return;
+
+ iface->pios = new_pios;
+ pio = &iface->pios[iface->pio_cnt];
+ iface->pio_cnt++;
+
+ memcpy(&pio->prefix, &p->nd_opt_pi_prefix, sizeof(pio->prefix));
+ pio->length = p->nd_opt_pi_prefix_len;
+ pio->lifetime = 0;
+
+ iface->pio_update = true;
+ syslog(LOG_INFO, "rfc9096: %s: add %s/%u",
+ iface->ifname,
+ inet_ntop(AF_INET6, &pio->prefix, ipv6_str, sizeof(ipv6_str)),
+ pio->length);
+}
+
+static void router_clear_ra_pio(time_t now,
+ struct interface *iface)
+{
+ size_t i = 0, pio_cnt = iface->pio_cnt;
+ char ipv6_str[INET6_ADDRSTRLEN];
+
+ while (i < iface->pio_cnt) {
+ struct ra_pio *cur_pio = &iface->pios[i];
+
+ if (ra_pio_expired(cur_pio, now)) {
+ syslog(LOG_INFO,
+ "rfc9096: %s: clear %s/%u",
+ iface->ifname,
+ inet_ntop(AF_INET6, &cur_pio->prefix, ipv6_str, sizeof(ipv6_str)),
+ cur_pio->length);
+
+ if (i + 1 < iface->pio_cnt)
+ iface->pios[i] = iface->pios[iface->pio_cnt - 1];
+
+ iface->pio_cnt--;
+ } else {
+ i++;
+ }
+ }
+
+ if (!iface->pio_cnt) {
+ free(iface->pios);
+ iface->pios = NULL;
+ } else if (iface->pio_cnt != pio_cnt) {
+ struct ra_pio *new_pios = realloc(iface->pios, sizeof(struct ra_pio) * iface->pio_cnt);
+
+ if (new_pios)
+ iface->pios = new_pios;
+ }
+}
+
+static void router_stale_ra_pio(struct interface *iface,
+ struct nd_opt_prefix_info *p,
+ time_t now)
+{
+ struct ra_pio *pio = router_find_ra_pio(iface, p);
+ char ipv6_str[INET6_ADDRSTRLEN];
+
+ if (!pio || pio->lifetime)
+ return;
+
+ pio->lifetime = now + iface->max_valid_lifetime;
+
+ iface->pio_update = true;
+ syslog(LOG_WARNING, "rfc9096: %s: stale %s/%u",
+ iface->ifname,
+ inet_ntop(AF_INET6, &pio->prefix, ipv6_str, sizeof(ipv6_str)),
+ pio->length);
+}
+
/* Router Advert server mode */
static int send_router_advert(struct interface *iface, const struct in6_addr *from)
{
struct sockaddr_in6 dest;
size_t dns_sz = 0, search_sz = 0, pref64_sz = 0, dnrs_sz = 0;
size_t pfxs_cnt = 0, routes_cnt = 0;
- size_t valid_addr_cnt = 0, invalid_addr_cnt = 0;
+ size_t total_addr_cnt = 0, valid_addr_cnt = 0;
/*
* lowest_found_lifetime stores the lowest lifetime of all prefixes;
* necessary to find longest adv interval necessary
bool valid_prefix = false;
char buf[INET6_ADDRSTRLEN];
+ router_clear_ra_pio(now, iface);
+
memset(&adv, 0, sizeof(adv));
adv.h.nd_ra_type = ND_ROUTER_ADVERT;
iov[IOV_RA_ADV].iov_len = sizeof(adv);
valid_addr_cnt = (iface->timer_rs.cb /* if not shutdown */ ? iface->addr6_len : 0);
- invalid_addr_cnt = iface->invalid_addr6_len;
// check ra_default
if (iface->default_router) {
valid_prefix = true;
}
- if (valid_addr_cnt + invalid_addr_cnt) {
- addrs = alloca(sizeof(*addrs) * (valid_addr_cnt + invalid_addr_cnt));
+ if (valid_addr_cnt + iface->pio_cnt) {
+ addrs = alloca(sizeof(*addrs) * (valid_addr_cnt + iface->pio_cnt));
if (valid_addr_cnt) {
memcpy(addrs, iface->addr6, sizeof(*addrs) * valid_addr_cnt);
+ total_addr_cnt = valid_addr_cnt;
/* Check default route */
if (!default_route && parse_routes(addrs, valid_addr_cnt))
default_route = true;
}
- if (invalid_addr_cnt) {
- size_t i = 0;
+ for (size_t i = 0; i < iface->pio_cnt; i++) {
+ struct ra_pio *cur_pio = &iface->pios[i];
+ bool pio_found = false;
- memcpy(&addrs[valid_addr_cnt], iface->invalid_addr6, sizeof(*addrs) * invalid_addr_cnt);
+ for (size_t j = 0; j < valid_addr_cnt; j++) {
+ struct odhcpd_ipaddr *cur_addr = &addrs[j];
- /* Remove invalid prefixes that were advertised 3 times */
- while (i < iface->invalid_addr6_len) {
- if (++iface->invalid_addr6[i].invalid_advertisements >= 3) {
- if (i + 1 < iface->invalid_addr6_len)
- memmove(&iface->invalid_addr6[i], &iface->invalid_addr6[i + 1], sizeof(*addrs) * (iface->invalid_addr6_len - i - 1));
-
- iface->invalid_addr6_len--;
+ if (cur_pio->length == cur_addr->prefix &&
+ !odhcpd_bmemcmp(&cur_pio->prefix, &cur_addr->addr.in6, cur_pio->length)) {
+ pio_found = true;
+ break;
+ }
+ }
- if (iface->invalid_addr6_len) {
- struct odhcpd_ipaddr *new_invalid_addr6 = realloc(iface->invalid_addr6, sizeof(*addrs) * iface->invalid_addr6_len);
+ if (!pio_found) {
+ struct odhcpd_ipaddr *addr = &addrs[total_addr_cnt];
- if (new_invalid_addr6)
- iface->invalid_addr6 = new_invalid_addr6;
- } else {
- free(iface->invalid_addr6);
- iface->invalid_addr6 = NULL;
- }
- } else
- ++i;
+ memcpy(&addr->addr.in6, &cur_pio->prefix, sizeof(addr->addr.in6));
+ addr->prefix = cur_pio->length;
+ addr->preferred_lt = 0;
+ addr->valid_lt = (uint32_t) (now + ND_VALID_LIMIT);
+ total_addr_cnt++;
}
}
}
/* Construct Prefix Information options */
- for (size_t i = 0; i < valid_addr_cnt + invalid_addr_cnt; ++i) {
+ for (size_t i = 0; i < total_addr_cnt; ++i) {
struct odhcpd_ipaddr *addr = &addrs[i];
struct nd_opt_prefix_info *p = NULL;
uint32_t preferred_lt = 0;
p->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
if (iface->ra_advrouter)
p->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_RADDR;
- p->nd_opt_pi_preferred_time = htonl(preferred_lt);
- p->nd_opt_pi_valid_time = htonl(valid_lt);
+ if (i >= valid_addr_cnt || !preferred_lt) {
+ /*
+ * RFC9096 § 3.5
+ *
+ * - Any prefixes that were previously advertised by the CE router
+ * via PIOs in RA messages, but that have now become stale, MUST
+ * be advertised with PIOs that have the "Valid Lifetime" and the
+ * "Preferred Lifetime" set to 0 and the "A" and "L" bits
+ * unchanged.
+ */
+ p->nd_opt_pi_preferred_time = 0;
+ p->nd_opt_pi_valid_time = 0;
+
+ router_stale_ra_pio(iface, p, now);
+ } else {
+ p->nd_opt_pi_preferred_time = htonl(preferred_lt);
+ p->nd_opt_pi_valid_time = htonl(valid_lt);
+
+ router_add_ra_pio(iface, p);
+ }
}
iov[IOV_RA_PFXS].iov_base = (char *)pfxs;
syslog(LOG_NOTICE, "Sending a RA on %s", iface->name);
- if (odhcpd_send(iface->router_event.uloop.fd, &dest, iov, ARRAY_SIZE(iov), iface) > 0)
+ if (odhcpd_send(iface->router_event.uloop.fd, &dest, iov, ARRAY_SIZE(iov), iface) > 0) {
iface->ra_sent++;
+ config_save_ra_pio(iface);
+ }
+
out:
free(pfxs);
free(routes);
return 0;
}
+static int handle_ra_pio(_unused struct ubus_context *ctx, _unused struct ubus_object *obj,
+ _unused struct ubus_request_data *req, _unused const char *method,
+ _unused struct blob_attr *msg)
+{
+ char ipv6_str[INET6_ADDRSTRLEN];
+ time_t now = odhcpd_time();
+ struct interface *iface;
+ void *interfaces_blob;
+
+ blob_buf_init(&b, 0);
+
+ interfaces_blob = blobmsg_open_table(&b, "interfaces");
+
+ avl_for_each_element(&interfaces, iface, avl) {
+ void *interface_blob;
+
+ if (iface->ra != MODE_SERVER)
+ continue;
+
+ interface_blob = blobmsg_open_array(&b, iface->ifname);
+
+ for (size_t i = 0; i < iface->pio_cnt; i++) {
+ struct ra_pio *cur_pio = &iface->pios[i];
+ void *cur_pio_blob;
+ uint32_t pio_lt;
+ bool pio_stale;
+
+ if (ra_pio_expired(cur_pio, now))
+ continue;
+
+ cur_pio_blob = blobmsg_open_table(&b, NULL);
+
+ pio_lt = ra_pio_lifetime(cur_pio, now);
+ pio_stale = ra_pio_stale(cur_pio);
+
+ inet_ntop(AF_INET6, &cur_pio->prefix, ipv6_str, sizeof(ipv6_str));
+
+ if (pio_lt)
+ blobmsg_add_u32(&b, "lifetime", pio_lt);
+ blobmsg_add_string(&b, "prefix", ipv6_str);
+ blobmsg_add_u16(&b, "length", cur_pio->length);
+ blobmsg_add_u8(&b, "stale", pio_stale);
+
+ blobmsg_close_table(&b, cur_pio_blob);
+ }
+
+ blobmsg_close_array(&b, interface_blob);
+ }
+
+ blobmsg_close_table(&b, interfaces_blob);
+
+ ubus_send_reply(ctx, req, b.head);
+
+ return 0;
+}
+
static int handle_add_lease(_unused struct ubus_context *ctx, _unused struct ubus_object *obj,
_unused struct ubus_request_data *req, _unused const char *method,
struct blob_attr *msg)
static struct ubus_method main_object_methods[] = {
{.name = "ipv4leases", .handler = handle_dhcpv4_leases},
{.name = "ipv6leases", .handler = handle_dhcpv6_leases},
+ {.name = "ipv6ra", .handler = handle_ra_pio},
UBUS_METHOD("add_lease", handle_add_lease, lease_attrs),
};